﻿using System;
using System.ComponentModel;
using System.Xml.Serialization;
using Microsoft.WowAddonStudio.FrameXml.Serialization.TypeConverters;
using System.Drawing;

namespace Microsoft.WowAddonStudio.FrameXml.Serialization
{
    [Serializable]
    [DesignerCategory("code")]
    [XmlType(Namespace = "http://www.blizzard.com/wow/ui/")]
    [TypeConverter(typeof(DimensionTypeConverter))]
    public class Dimension : SerializationObject, ICloneable, ICopiable<Dimension>
    {
        private int? x, y;

        /// <summary>
        /// Gets or sets the dimension expressed as absolute values.
        /// </summary>
        [XmlElement]
        public AbsDimension AbsDimension { get; set; }

        /// <summary>
        /// Gets or sets the dimension expressed as relative value.
        /// </summary>
        [XmlElement]
        public RelDimension RelDimension { get; set; }

        /// <summary>
        /// Gets a value indicating whether this <see cref="Dimension"/> is relative.
        /// </summary>
        [XmlIgnore]
        public bool IsRelative
        {
            get { return RelDimension != null; }
        }

        /// <summary>
        /// Gets a value indicating whether this <see cref="Dimension"/> is absolute.
        /// </summary>
        [XmlIgnore]
        public bool IsAbsolute
        {
            get { return !IsRelative; }
        }

        /// <summary>
        /// Gets a value indicating whether a serialization object is complete and can be rendered.
        /// </summary>
        /// <value></value>
        public override bool IsComplete
        {
            get
            {
                return IsRelative || IsAbsolute;
            }
        }

        /// <summary>
        /// Gets or sets the value of X. This property is optional.
        /// </summary>
        [XmlIgnore]
        public int? X
        {
            get { return x; }
            set { x = value; }
        }

        /// <summary>
        /// Gets or sets the value of X. This property is optional.
        /// </summary>
        [XmlAttribute("x")]
        [Browsable(false)]
        public int XValue
        {
            get { return x.Value; }
            set { x = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether a value for <see cref="X"/> was specified. This property is not serialized.
        /// </summary>
        [XmlIgnore]
        public bool XValueSpecified
        {
            get { return x.HasValue; }
        }

        /// <summary>
        /// Gets or sets the value of Y. This property is optional.
        /// </summary>
        [XmlIgnore]
        public int? Y
        {
            get { return y; }
            set { y = value; }
        }

        /// <summary>
        /// Gets or sets the value of Y. This property is optional.
        /// </summary>
        [XmlAttribute("y")]
        [Browsable(false)]
        public int YValue
        {
            get { return y.Value; }
            set { y = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether a value for <see cref="Y"/> was specified. This property is not serialized.
        /// </summary>
        [XmlIgnore]
        public bool YValueSpecified
        {
            get { return y.HasValue; }
        }

        /// <summary>
        /// Converts the dimension to a <see cref="Point"/>.
        /// </summary>
        /// <param name="invertY">True, if the value of Y should be inverted.</param>
        /// <returns>A <see cref="Point"/> structure.</returns>
        public Point ToPoint(bool invertY)
        {
            if (IsRelative)
                throw new InvalidOperationException("Only absolute dimensions can be convert to Points.");

            if (IsAbsolute)
            {
                if (AbsDimension != null)
                    return AbsDimension.ToPoint(invertY);

                if (X.HasValue && Y.HasValue)
                    return new Point(X.Value, invertY ? -Y.Value : Y.Value);
            }

            return new Point();
        }

        /// <summary>
        /// Converts the dimension to a <see cref="Point"/> and inverts the Y coordinate.
        /// </summary>
        /// <returns>A <see cref="Point"/> structure.</returns>
        public Point ToPoint()
        {
            return this.ToPoint(true);
        }

        public Size ToSize()
        {
            if (IsAbsolute)
            {
                if (AbsDimension != null)
                    return AbsDimension.ToSize();

                return new Size(X ?? 0, Y ?? 0);
            }

            return new Size();
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>The cloned instance.</returns>
        public object Clone()
        {
            return this.Copy();
        }

        /// <summary>
        /// Creates a new object that is a copy of the instance.
        /// </summary>
        /// <returns>The copied instance.</returns>
        /// <remarks>Performs a deep copy.</remarks>
        public Dimension Copy()
        {
            return new Dimension
            {
                X = X,
                Y = Y,
                AbsDimension = AbsDimension != null ? AbsDimension.Copy() : null,
                RelDimension = RelDimension != null ? RelDimension.Copy() : null
            };
        }

        /// <summary>
        /// Equalses the specified obj.
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public bool Equals(Dimension obj)
        {
            if (ReferenceEquals(null, obj))
                return false;
            if (ReferenceEquals(this, obj))
                return true;

            return Equals(obj.RelDimension, RelDimension) && Equals(obj.AbsDimension, AbsDimension) && obj.y.Equals(y) && obj.x.Equals(x);
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">
        /// The <paramref name="obj"/> parameter is null.
        /// </exception>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
                return false;
            if (ReferenceEquals(this, obj))
                return true;
            if (obj.GetType() != typeof (Dimension))
                return false;
            return Equals((Dimension) obj);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int result = (RelDimension != null ? RelDimension.GetHashCode() : 0);
                result = (result*397) ^ (AbsDimension != null ? AbsDimension.GetHashCode() : 0);
                result = (result*397) ^ (y.HasValue ? y.Value : 0);
                result = (result*397) ^ (x.HasValue ? x.Value : 0);
                return result;
            }
        }

        /// <summary>
		/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
		/// </returns>
    	public override string ToString()
    	{
            if (X.HasValue)
                return String.Format("Dimension: [X = {0}, Y = {1}]", X.Value, Y.Value);

            if(AbsDimension != null)
                return String.Format("Dimension: [X = {0}, Y = {1}]", AbsDimension.X, AbsDimension.Y);

            if (RelDimension != null)
                return String.Format("Dimension: [X = {0}, Y = {1}]", RelDimension.X, RelDimension.Y);

		    return string.Empty;
    	}
    }
}